﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Threading;

using Microsoft.Practices.Composite.Events;
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.Presentation.Events;
using Microsoft.Practices.Composite.Regions;
using Microsoft.Practices.Unity;

using Matchingo.Common.Constants;
using Matchingo.Common.Types.Types;
using Matchingo.Common.Types.EventArguments;
using Matchingo.Common.Types.Events;
using Matchingo.Common.Types.Interfaces;

namespace Matchingo.Modules.StatusBarModule
{
    // TODO:: this ViewModel is using both dependency properties and INotifyPropertyChanged, shouldn't need both
    public class StatusBarViewModel : DependencyObject, IModule, INotifyPropertyChanged
    {
        #region ========== Private Constants ==========

        private const string DEFAULT_PLAYER_NAME = CommonConstants.GAME_SETTINGS_DEFAULT_PLAYERNAME;

        #endregion

        #region ========== Private Properties ==========

        private readonly IUnityContainer Container;
        private readonly IEventAggregator EventAggregator;
        private readonly IRegionManager RegionManager;
        private readonly IMemoryGameLogic GameLogic;
        private readonly IHighScoreProvider ScoreProvider;

        private IStatusBarView View { get; set; }

        #endregion

        #region ========== Public Dependency Properties ==========

        #region PlayerScore

        /// <summary>
        /// The score to display next to the player's name
        /// </summary>
        public int PlayerScore
        {
            get { return (int)GetValue(PlayerScoreProperty); }
            set 
            { 
                SetValue(PlayerScoreProperty, value);
                NotifyPropertyChanged("PlayerScore");
            }
        }

        public static readonly DependencyProperty PlayerScoreProperty =
            DependencyProperty.Register("PlayerScore", typeof(int), typeof(StatusBarViewModel),
              new PropertyMetadata(0));

        #endregion

        #region HighScore

        /// <summary>
        /// The score to display as the high score
        /// </summary>
        public int HighScore
        {
            get { return (int)GetValue(HighScoreProperty); }
            set 
            { 
                SetValue(HighScoreProperty, value);
                NotifyPropertyChanged("HighScore");
            }
        }

        public static readonly DependencyProperty HighScoreProperty =
            DependencyProperty.Register("HighScore", typeof(int), typeof(StatusBarViewModel),
              new PropertyMetadata(0));

        #endregion

        #region PlayersName

        /// <summary>
        /// The name to display next to the user's high score
        /// </summary>
        public string PlayersName
        {
            get { return (string)GetValue(PlayersNameProperty); }
            set 
            { 
                SetValue(PlayersNameProperty, value);
                NotifyPropertyChanged("PlayersName");
            }
        }

        public static readonly DependencyProperty PlayersNameProperty =
            DependencyProperty.Register("PlayersName", typeof(string), typeof(StatusBarViewModel),
              new PropertyMetadata(string.Empty));

        #endregion

        #region TimeElapsed

        /// <summary>
        /// The timer of how many seconds this game has been going
        /// </summary>
        public TimeSpan TimeElapsed
        {
            get { return (TimeSpan)GetValue(TimeElapsedProperty); }
            set
            {
                SetValue(TimeElapsedProperty, value);
                NotifyPropertyChanged("TimeElapsed");
            }
        }

        public static readonly DependencyProperty TimeElapsedProperty =
            DependencyProperty.Register("TimeElapsed", typeof(TimeSpan), typeof(StatusBarViewModel),
              new PropertyMetadata(TimeSpan.Zero));

        #endregion

        #endregion

        #region ========== Constructor ==========

        public StatusBarViewModel(IUnityContainer container,
            IEventAggregator eventAggregator, 
            IRegionManager regionManager,
            IMemoryGameLogic gameLogic, 
            IHighScoreProvider scoreProvider)
        {
            Container = container;
            GameLogic = gameLogic;
            ScoreProvider = scoreProvider;            
            RegionManager = regionManager;
            EventAggregator = eventAggregator;
        }

        #endregion

        #region ========== Public Methods ==========

        public void Initialize()
        {
            View = Container.Resolve<IStatusBarView>();
            View.Initialize(this);

            IRegion region = RegionManager.Regions[CommonConstants.SHELL_REGION_Footer];
            region.Add(View);

            UpdateFields(); // do an initial update
            SinkEvents(); // subscribe to the events which will update public properties
        }

        #endregion

        #region ========== Private Methods ==========

        private void SinkEvents()
        {
            GameLogic.MatchComboCountChanged += (s, e) => UpdateComboTextPulse();
            ScoreProvider.RecordAdded += (s, e) => UpdateFields();
            EventAggregator.GetEvent<GameStartedEvent>().Subscribe(GameStartedFired, ThreadOption.UIThread, true);
            EventAggregator.GetEvent<GameEndedEvent>().Subscribe(GameEndedFired, ThreadOption.UIThread, true);            

            // NOTE:: our statusbar contains the timer, which is a timespan and will not fire an update event, 
            // so we need to update this window manditorily every XX seconds
            DispatcherTimer dt = new DispatcherTimer();
            dt.Interval = new TimeSpan(0, 0, 0, 0, CommonConstants.STATUSBAR_AUTO_UPDATE_POLLING_PERIOD);            
            dt.Tick += (s, e) => UpdateFields();
            dt.Start();
        }

        private void UpdateComboTextPulse()
        {
            // see if the combocount is more than 1, if so then kickoff the 
            if (GameLogic.MatchComboCount > 1)
            {
                View.StartComboPulse(string.Format("{0}X", GameLogic.MatchComboCount));
            }
            else
            {
                View.StopComboPulse();
            }
        }

        private void UpdateDemoReplayTextPulse()
        {
            View.StopTextPulse();

            // see if this is a demo or a replay, if so start the corresponding animation
            if (GameLogic.CurrentGame.ReplayGame)
            {
                View.StartTextPulse("Replay");
            }
            else if (GameLogic.CurrentGame.ArtificialGame)
            {
                View.StartTextPulse("Demo");
            }
        }

        private void UpdateFields()
        {
            // this must all be done on a UI thread
            Application.Current.RootVisual.Dispatcher.BeginInvoke(() =>
            {
                PlayersName = (GameLogic.CurrentGame != null) ? GameLogic.CurrentGame.PlayerName : DEFAULT_PLAYER_NAME;

                // time elapsed
                TimeElapsed = (GameLogic.CurrentGame != null) ? GameLogic.CurrentGame.GameTimeElapsed : TimeSpan.Zero;

                // player score is the current game's score, unless there is no current game in which case it's 0
                PlayerScore = (GameLogic.CurrentGame == null) ? 0 : GameLogic.CurrentGame.Score;

                // get the top score from the ScoreProvider, return that score if able, 0 otherwise
				List<Game> games = ScoreProvider.GetHighScoresByCardCount(1, (GameLogic.CurrentGame == null) ? CommonConstants.GAME_SETTINGS_DEFAULT_CARDCOUNT : GameLogic.CurrentGame.CardCount);
                HighScore = (games.Count == 1) ? games[0].Score : 0;
            });
        }

        private void NotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged == null)
                return;

            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region ========== Event Handlers ==========

        // NOTE:: events attached to the EventAggregator need to be public in Silverlight
        public void GameStartedFired(Game g)
        {
            UpdateFields();
            UpdateDemoReplayTextPulse();
        }

        // NOTE:: events attached to the EventAggregator need to be public in Silverlight
        public void GameEndedFired(GameEndedEventArgs args)
        {
            UpdateFields();
            View.StopTextPulse();
        }        

        #endregion      

        #region =========== Public Events ==========

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}
